#!/usr/bin/env python3

# Generate a Make fragment to describe package's dependencies.
# This alows Make to build the dependency graph for a package.

from __future__ import annotations

import argparse
import pathlib
import re


ASSIGN_RE = re.compile(r"^\s*PKG_(NAME|VER|DEPS)\s*(?::=|\+=|=)\s*(.*)$")
RESET_VARS = (
    "PKG_NAME",
    "PKG_VER",
    "PKG_DEPS",
    "PKG_WORK_DIR",
    "PKG_SOURCE_STAMP",
    "PKG_PATCH_STAMP",
    "PKG_BUILD_STAMP",
    "PKG_INSTALL_STAMP",
)


def sanitise(name: str) -> str:
    return re.sub(r"[^A-Za-z0-9_]+", "_", name)


def parse_makefile(path: pathlib.Path) -> tuple[str | None, str | None, list[str]]:
    text = path.read_text(encoding="utf-8")
    buffer = ""
    name: str | None = None
    version: str | None = None
    deps: list[str] = []

    for raw in text.splitlines():
        stripped = raw.split("#", 1)[0].rstrip()
        if not stripped and not buffer:
            continue

        if stripped.endswith("\\"):
            buffer += stripped[:-1] + " "
            continue

        if buffer:
            stripped = buffer + stripped
            buffer = ""

        match = ASSIGN_RE.match(stripped)
        if not match:
            continue

        key, rhs = match.groups()
        rhs = rhs.strip()
        if not rhs:
            continue

        if key == "DEPS":
            deps.extend(rhs.split())
        elif key == "NAME" and name is None:
            name = rhs.split()[0]
        elif key == "VER" and version is None:
            version = rhs.split()[0]

    ordered: list[str] = []
    seen: set[str] = set()
    for dep in deps:
        if dep not in seen:
            seen.add(dep)
            ordered.append(dep)

    return name, version, ordered


def render(
    *,
    pkg_identifier: str,
    pkg_name: str | None,
    pkg_version: str | None,
    deps: list[str],
    pkg_dir: pathlib.Path,
    pkg_env: pathlib.Path,
) -> str:
    actual_name = pkg_name or pkg_identifier
    pkg_id = sanitise(actual_name)
    dep_ids = [(dep, sanitise(dep)) for dep in deps]

    lines: list[str] = []
    lines.append("# Autogenerated")
    lines.append("")
    lines.append(f"PKG_NAME := {actual_name}")
    if pkg_version:
        lines.append(f"PKG_VER := {pkg_version}")
    if deps:
        lines.append(f"PKG_DEPS := {' '.join(deps)}")
    lines.append(f"include {pkg_env}")
    lines.append("")

    lines.append(f"PKG_DIR_{pkg_id} := {pkg_dir}")
    lines.append(f"PKG_STAMP_{pkg_id} := $(PKG_INSTALL_STAMP)")
    if pkg_version:
        lines.append(f"PKG_VER_{pkg_id} := $(PKG_VER)")
    lines.append(f"PKG_DEPS_{pkg_id} := {' '.join(deps) if deps else ''}")
    lines.append("")

    if deps:
        for dep in deps:
            lines.append(f"-include $(DEP_DIR)/{dep}.deps.mk")
        lines.append("")

    lines.append(f"ALL_PACKAGE_STAMPS += $(PKG_STAMP_{pkg_id})")
    lines.append("")

    lines.append(f".PHONY: {actual_name}")
    lines.append(f"{actual_name}: $(PKG_STAMP_{pkg_id})")
    lines.append("")

    if deps:
        prereqs = " ".join(f"$(PKG_STAMP_{d_id})" for _, d_id in dep_ids)
        lines.append(f"$(PKG_STAMP_{pkg_id}): {prereqs}")
    else:
        lines.append(f"$(PKG_STAMP_{pkg_id}):")
    if pkg_version:
        lines.append(f"\t@echo \"==> Build {actual_name} {pkg_version}\"")
    else:
        sys.stderr.write("no version\n")
        sys.exit(1)
    lines.append(f"\t@$(MAKE) -C $(PKG_DIR_{pkg_id}) --no-print-directory")
    lines.append("")

    for var in RESET_VARS:
        lines.append(f"undefine {var}")
    lines.append("")

    return "\n".join(lines)


def main() -> None:
    parser = argparse.ArgumentParser(description="Generate a package depfile")
    parser.add_argument("--package", required=True)
    parser.add_argument("--makefile", required=True, type=pathlib.Path)
    parser.add_argument("--pkg-dir", required=True, type=pathlib.Path)
    parser.add_argument("--pkg-env", required=True, type=pathlib.Path)
    parser.add_argument("--out", required=True, type=pathlib.Path)
    args = parser.parse_args()

    name, version, deps = parse_makefile(args.makefile)

    args.out.parent.mkdir(parents=True, exist_ok=True)
    content = render(
        pkg_identifier=args.package,
        pkg_name=name,
        pkg_version=version,
        deps=deps,
        pkg_dir=args.pkg_dir.resolve(),
        pkg_env=args.pkg_env.resolve(),
    )
    args.out.write_text(content + "\n", encoding="utf-8")


if __name__ == "__main__":
    main()

